/*
 * Copyright (c) 2010, Thiago Campos Viana.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Thiago Campos Viana nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package kernel.networkcode;

import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;
import kernel.gamecode.GameLoopThread;
import kernel.gamecode.Player;
import java.io.*;
import java.net.*;
import java.nio.ByteBuffer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.*;
import lib.db.DataBase;

/**
 *
 * @author Thiago Campos Viana
 */
public class NetworkListenerThread implements Runnable {

    private static final int FRAME_DELAY = 1; // 20ms. implies 50fps (1000/20) = 50
    private static final int RECEIVE_SIZE = 517;
    /**
     *
     */
    protected long cycleTime;
    /**
     *
     */
    protected boolean active;
    private static NetworkListenerThread _instance;
    /**
     *
     */
    protected DatagramSocket socket = null;
    /**
     *
     */
    protected BufferedReader in = null;
    //protected boolean moreQuotes = true;
    ByteBuffer resposta;
    private SimpleDateFormat formatter;

    /**
     *
     * @throws IOException
     */
    public NetworkListenerThread() throws IOException {
        this("GameServer");
        formatter = new SimpleDateFormat("EEE, dd-MMM-yyyy HH:mm:ss.SSS");
    }

    /**
     *
     * @return
     */
    public static synchronized NetworkListenerThread getInstance() {
        if (_instance == null) {
            try {
                _instance = new NetworkListenerThread();
            } catch (IOException ex) {
            }
        }

        return _instance;
    }

    /**
     *
     * @param name
     * @throws IOException
     */
    public NetworkListenerThread(String name) throws IOException {
        active = true;
        GameLoopThread.getInstance().players.clear();
        socket = new DatagramSocket(4445);
        socket.setReceiveBufferSize(RECEIVE_SIZE);

    }

    public void printMessage(String message) {

        System.out.println(formatter.format(new java.util.Date()) + " - " + message);

    }

    @Override
    public void run() {
        printMessage("Server Started");
        cycleTime = System.currentTimeMillis();
        while (active) {
            update();
            synchFramerate();

        }
        socket.close();
    }

    private void synchFramerate() {
        cycleTime = cycleTime + FRAME_DELAY;
        long difference = cycleTime - System.currentTimeMillis();
        //System.out.println(difference);
        try {
            Thread.sleep(Math.max(0, difference));
        } catch (InterruptedException e) {
        }
    }

    private byte[] createPlayer(byte[] buf, DatagramPacket packet) {
        resposta = ByteBuffer.allocate(5);
        byte[] name = new byte[buf[1]];
        int x;
        for (x = 0; x < name.length; x++) {
            name[x] = buf[x + 2];
        }

        byte[] pass = new byte[buf[x + 2]];
        x++;
        for (int i = 0; i < pass.length; i++) {
            pass[i] = buf[x + 2];
            x++;
        }

        System.out.println(new String(pass));
        //verify if player is already loged in
        Iterator<Player> it = GameLoopThread.getInstance().players.iterator();
        while (it.hasNext()) {
            if (it.next().name.equals(new String(name))) {
                byte[] r = {0};
                printMessage("Player failed to connect: " + new String(name));
                return r;
            }
        }
        //validate user login and pass
        Player p = null;
        try {
            Connection con = null;
            con = DataBase.getInstance().getConnection();

            PreparedStatement pstmt = con.prepareStatement("select * from player where login = ?  and password_hash = ?");

            pstmt.setString(1, new String(name));
            String password = (new String(name)) + "\n" + (new String(pass));


            password = lib.Md5.encodeMd5(password);

            pstmt.setString(2, password);

            ResultSet rs = pstmt.executeQuery();

            if (rs.next()) {
                System.out.println(rs.getString("login"));
                p = new Player(rs, packet.getAddress());
            } else {
                byte[] r = {0};
                printMessage("Player failed to connect: " + new String(name));
                return r;
            }
        } catch (Exception ie) {
            ie.printStackTrace();
        }

        GameLoopThread.getInstance().addPlayer(p);

        printMessage("New player connected: " + new String(name) + "(" + p.id + ")");
        resposta.put((byte) 1);
        resposta.putInt(p.id);


        return resposta.array();


    }

    private byte[] updatePlayer(byte[] buf, InetAddress address) {


        Iterator<Player> it = GameLoopThread.getInstance().players.iterator();
        resposta = ByteBuffer.allocate(0);
        ByteBuffer bytes = ByteBuffer.allocate(buf.length);
        bytes.put(buf);
        bytes.rewind();
        bytes.get();
        int pid = bytes.getInt();


        while (it.hasNext()) {

            Player p = it.next();

            if (p.address.equals(address) && p.id == pid) {
                //System.out.println(p.region.bytes.limit());
                p.move(buf[5]);
                try {
                    resposta = ByteBuffer.allocate(p.region.bytes.array().length + 5);
                } catch (Exception ie) {
                    resposta = ByteBuffer.allocate(5 + 12);
                }

                resposta.put(p.layer);
                resposta.putShort(p.x);
                resposta.putShort(p.y);
                try {
                    resposta.put(p.region.bytes.array());
                } catch (Exception ie) {
                    resposta.put(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
                }

            }
        }

        return resposta.array();


    }

    private byte[] removePlayer(byte[] buf, InetAddress address) {


        Iterator<Player> it = GameLoopThread.getInstance().players.iterator();
        resposta = ByteBuffer.allocate(1);
        ByteBuffer bytes = ByteBuffer.allocate(buf.length);
        bytes.put(buf);
        bytes.rewind();
        bytes.get();
        int pid = bytes.getInt();


        while (it.hasNext()) {

            Player p = it.next();

            if (p.address.equals(address) && p.id == pid) {
                //System.out.println(p.region.bytes.limit());
                GameLoopThread.getInstance().removePlayer(p);
                resposta.put((byte) 1);
                break;

            }
        }

        return resposta.array();


    }

    private byte[] updateChat(byte[] buf, InetAddress address) {

        return new byte[]{0};
    }

    /**
     *
     */ 
    public void update() {

        try {
            byte[] buf = new byte[256];

            // receive request
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            socket.receive(packet);

            // figure out response
            byte query = packet.getData()[0];

            InetAddress address = packet.getAddress();

            switch (query) {
                case 0:
                    createPlayer(packet.getData(), packet);
                    break;
                case 1:
                    updatePlayer(packet.getData(), packet.getAddress());
                    break;
                case 2:
                    updateChat(packet.getData(), packet.getAddress());
                    break;
                case 3:
                    removePlayer(packet.getData(), packet.getAddress());
                    break;
                default:
                    break;

            }

            // send the response to the client at "address" and "port"

            int port = packet.getPort();
            packet = new DatagramPacket(resposta.array(), resposta.array().length, address, port);

            socket.send(packet);
        } catch (IOException e) {
            active = false;
        }
    }
}
